#ifndef PORTABLE_FORMAT_HELPER_H_
#define PORTABLE_FORMAT_HELPER_H_

#include <iostream>
#include <string_view>

#include "src/pbc/portable-format.h"

namespace portable {

class PbcScript;

class PbcBytecode {
 public:
  explicit PbcBytecode(uintptr_t address) : address_(address) {}

  const uint8_t* FirstBytecode() const { return Data()->bytes_; }
  uint32_t BytecodeSizeInBytes() const { return Data()->length_; }
  void Dump(const PbcScript& module) const;

 private:
  const RawBytecode* Data() const {
    return reinterpret_cast<const RawBytecode*>(address_);
  }

  const uintptr_t address_;
};

class PbcConstantTable {
 public:
  explicit PbcConstantTable(const uintptr_t address) : address_(address) {}

  const PbcValue* Entries() const { return Data()->entries_; }
  const PbcShortValue* ShortEntries() const { return Data()->short_entries_; }
  bool IsShortValue() const { return Data()->is_short_value; }
  uint32_t NumberOfEntries() const {
    return static_cast<uint32_t>(Data()->length_);
  }
  void DumpShortValue(const PbcScript& module) const;
  void Dump(const PbcScript& module) const;

 private:
  const RawConstantTable* Data() const {
    return reinterpret_cast<const RawConstantTable*>(address_);
  }

  const uintptr_t address_;
};

// Base class for Array and Object literals
class AggregateLiteral {
 public:
  enum Flags {
    kNoFlags = 0,
    kIsShallow = 1,
    kDisableMementos = 1 << 1,
    kNeedsInitialAllocationSite = 1 << 2,
    kIsShallowAndDisableMementos = kIsShallow | kDisableMementos,
  };
};

class ObjectLiteral final : public AggregateLiteral {
 public:
  enum Flags {
    kFastElements = 1 << 3,
    kHasNullPrototype = 1 << 4,
  };
};

class PbcDefaultObjectLiteral {
 public:
  explicit PbcDefaultObjectLiteral(const uintptr_t address)
      : address_(address) {}
  ObjectDescript GetDescrit() {
    return ObjectDescript{
        .boilerplate_description_flag_ = ObjectLiteral::kFastElements,
        .boilerplate_properties_ = this->NumberOfProperties(),
        .property_length_ = this->NumberOfProperties(),
        .index_keys_ = 0,
        .has_seen_proto_ = false};
  }
  uint32_t NumberOfProperties() const {
    return static_cast<uint32_t>(Data().number_of_properties_);
  }
  bool IsShortValue() const { return Data().is_short_value_; }
  PbcProperty GetProperty(uint32_t index) const {
    P_CHECK(index < NumberOfProperties());
    return Data().properties_[index];
  }
  PbcShortProperty GetShortProperty(uint32_t index) const {
    P_CHECK(index < NumberOfProperties());
    return Data().short_properties_[index];
  }

 private:
  const RawDefaultObjectLiteral& Data() const {
    return *reinterpret_cast<const RawDefaultObjectLiteral*>(address_);
  }

  const uintptr_t address_;
};

// Literal interfaces
class PbcObjectLiteral {
 public:
  explicit PbcObjectLiteral(const uintptr_t address) : address_(address) {}

  // todo(hh): spell check
  ObjectDescript GetDescrit() { return Data().desc_; }
  uint32_t NumberOfProperties() const {
    return static_cast<uint32_t>(Data().number_of_properties_);
  }
  PbcProperty GetProperty(uint32_t index) const {
    P_CHECK(index < NumberOfProperties());
    return Data().properties_[index];
  }

 private:
  const RawObjectLiteral& Data() const {
    return *reinterpret_cast<const RawObjectLiteral*>(address_);
  }

  const uintptr_t address_;
};

class PbcArrayLiteral {
 public:
  explicit PbcArrayLiteral(const uintptr_t address) : address_(address) {}

  bool is_cow() const { return static_cast<bool>(Data().flags_.is_cow_); }
  uint8_t kind() const { return static_cast<uint8_t>(Data().flags_.kind_); }
  uint32_t NumberOfElements() const {
    return static_cast<uint32_t>(Data().length_);
  }
  bool IsShortValue() const { return Data().is_short_value_; }
  PbcValue GetElement(uint32_t index) const {
    P_CHECK(index < NumberOfElements());
    return Data().elements_[index];
  }
  PbcShortValue GetShortElement(uint32_t index) const {
    P_CHECK(index < NumberOfElements());
    return Data().short_elements_[index];
  }

 private:
  const RawArrayLiteral& Data() const {
    return *reinterpret_cast<const RawArrayLiteral*>(address_);
  }

  const uintptr_t address_;
};

class PbcClassProperty {
 public:
  explicit PbcClassProperty(const uintptr_t address) : address_(address) {}

  bool isStatic() { return Data().flags_.is_static_; }

  bool isPrivate() { return Data().flags_.is_private_; }

  bool isComputedName() { return Data().flags_.is_computed_name_; }

  ClassPropertyKind kind() {
    return static_cast<ClassPropertyKind>(Data().flags_.kind_);
  }

  PbcValue key() { return Data().key_; }

 private:
  const ClassProperty& Data() const {
    return *reinterpret_cast<const ClassProperty*>(address_);
  }

  const uintptr_t address_;
};

class PbcClassLiteral {
 public:
  explicit PbcClassLiteral(const uintptr_t address) : address_(address) {}

  uint32_t getStartPosition() { return Data().start_; }

  uint32_t getEndPosition() { return Data().end_; }

  uint64_t getNumberOfProperties() { return Data().number_of_properties_; }

  PbcClassProperty getProperty(uint64_t idx) {
    return PbcClassProperty(
        reinterpret_cast<uintptr_t>(&Data().properties_[idx]));
  }

 private:
  const RawClassLiteral& Data() const {
    return *reinterpret_cast<const RawClassLiteral*>(address_);
  }

  const uintptr_t address_;
};

class PbcTemplateLiteral {
 public:
  explicit PbcTemplateLiteral(const uintptr_t address) : address_(address) {}

  uint32_t Length() const { return Data().length_; }
  bool IsDuplicated() const { return Data().flags_.duplicated_; }
  StringRef GetRawString(uint32_t index) const {
    P_DCHECK(index < Length());
    const PbcValue& raw_string = Data().raw_and_cookeds_[index];
    P_DCHECK(raw_string.Kind() == PbcValueKind::kU8String ||
             raw_string.Kind() == PbcValueKind::kU16String);
    return static_cast<const StringRef&>(raw_string);
  }
  PbcValue GetCookedString(uint32_t index) const {
    P_DCHECK(index < Length());
    if (IsDuplicated()) {
      return GetRawString(index);
    }
    const PbcValue* cooked_strings = &Data().raw_and_cookeds_[Length()];
    const PbcValue& cooked_string = cooked_strings[index];
    P_DCHECK(cooked_string.Kind() == PbcValueKind::kU8String ||
             cooked_string.Kind() == PbcValueKind::kU16String ||
             (cooked_string.Kind() == PbcValueKind::kUniqueValue &&
              static_cast<const UniqueScalar&>(cooked_string).Tag() ==
                  kTagUndefined));
    return cooked_string;
  }

 private:
  const RawTemplateLiteral& Data() const {
    return *reinterpret_cast<const RawTemplateLiteral*>(address_);
  }
  const uintptr_t address_;
};

class PbcFeedbackMeta {
 public:
  explicit PbcFeedbackMeta(uintptr_t address) : address_(address) {}

  uint32_t NumberOfKinds() const { return Data().slot_count_; }
  uint32_t NumberOfClosureParameterCounts() const {
    return Data().create_closure_slot_count_;
  }
  uint32_t GetKind(uint32_t index) const {
    P_DCHECK(index < NumberOfKinds());
    return Data().entries_[index];
  }
  uint16_t GetClosureParameterCount(uint32_t index) const {
    P_DCHECK(index < NumberOfClosureParameterCounts());
    return reinterpret_cast<const uint16_t*>(
        &Data().entries_[NumberOfKinds()])[index];
  }
  void Dump(const PbcScript& module) const;

 private:
  const RawFeedbackMeta& Data() const {
    return *reinterpret_cast<const RawFeedbackMeta*>(address_);
  }
  const uintptr_t address_;
};

class PbcFunction {
 public:
  explicit PbcFunction(uintptr_t address) : address_(address) {}

  PbcBytecode GetBytecode(const PbcScript& module) const;
  PbcConstantTable GetConstantTable(const PbcScript& module) const;
  StringRef GetName() const;
  bool IsNullName() const;

  uint32_t GetFunctionId(const PbcScript& module) const;
  uint32_t GetScopeId(const PbcScript& module) const {
    // The scope_id in Function Scope is the same as func_id.
    return GetFunctionId(module);
  }

  // Flags accessors
  PbcFunctionKind GetFunctionKind() const {
    return GetFunctionHeader()->flags_.kind_;
  }
  PbcFunctionSyntaxKind GetFunctionSyntaxKind() const {
    return GetFunctionHeader()->flags_.syntax_kind_;
  }
  bool IsCopyable() const { return GetFunctionHeader()->flags_.is_copyable_; }
  bool IsGenerator() const { return GetFunctionHeader()->flags_.is_generator_; }
  bool IsAsync() const { return GetFunctionHeader()->flags_.is_async_; }
  bool IsStrictMode() const {
    return GetFunctionHeader()->flags_.is_strict_mode_;
  }

  bool IsTopLevelFunction(const PbcScript& module) const;
  uint32_t GetRegisterCount() const {
    return GetFunctionHeader()->register_count_;
  }
  uint16_t GetParameterCount() const {
    return GetFunctionHeader()->parameter_count_;
  }
  uint32_t GetExceptionCount() const {
    return GetFunctionHeader()->exception_count_;
  }
  uint32_t GetIncomingNewTargetOrGeneratorRegister() const {
    uint32_t index =
        GetFunctionHeader()->incoming_new_target_or_generator_register_;
    if (index == ((1 << INCOMINT_NEW_TARGET_OR_GENERATOR_REGISTER_BIT) - 1)) {
      return kInvalidRegister;
    }
    return index;
  }
  uint16_t GetLength() const { return GetFunctionHeader()->length_; }
  uint32_t GetSuspendCount() const {
    return GetFunctionHeader()->suspend_count_;
  }
  uint8_t GetExpectedNofProperties() {
    return GetFunctionHeader()->expected_nof_properties_;
  }
  bool GetRequiresInstanceMembersInitializer() {
    return GetFunctionHeader()->flags_.requires_instance_members_initializer_;
  }
  void DumpHeader(const PbcScript& module) const;
  void Dump(const PbcScript& module) const;

 private:
  const FunctionHeader* GetFunctionHeader() const {
    return reinterpret_cast<const FunctionHeader*>(address_);
  }

  uintptr_t address_;  // The address of function header
};

static_assert(sizeof(PbcFunction) == 8);

class PbcLocalVar {
 public:
  explicit PbcLocalVar(uintptr_t address) : address_(address) {}

  StringRef GetName() const;

 private:
  const LocalVar* Impl() const { return reinterpret_cast<LocalVar*>(address_); }
  uintptr_t address_;
};

class PbcScope {
 public:
  static PbcScope InvalidScope();
  explicit PbcScope(uintptr_t address) : address_(address) {}
  PbcScope(const PbcScope& scope) : address_(scope.address_) {}

  bool IsValid() const { return address_ != 0; }
  ScopeType GetScopeType() const { return Flags().scope_type_; }
  bool IsStrictMode() const { return Flags().is_strict_mode_; }
  bool HasReceiver() const { return Flags().has_receiver_; }
  uint32_t SizeInByte() const {
    // uint32_t size_in_byte =
    //     sizeof(RawScope) + sizeof(LocalVar) * NumberOfLocalVars();
    return sizeof(RawScope);
  }
  PbcScope GetNextScope() const { return PbcScope(address_ + SizeInByte()); }

  PbcFunction GetFunction(const PbcScript& module) const;
  uint32_t GetOuterScopeId() const;

  uint32_t NumberOfLocalVars() const { return Data()->num_local_vars_; }
  PbcLocalVar GetLocalVar(uint32_t index, const PbcScript& module) const;
  void Dump(const PbcScript& module) const;

 private:
  inline ScopeFlags Flags() const {
    uint8_t raw = Data()->flags_;
    return *reinterpret_cast<ScopeFlags*>(&raw);
  }
  const RawScope* Data() const {
    P_CHECK(IsValid());
    return reinterpret_cast<const RawScope*>(address_);
  }
  const uintptr_t address_;
};

// The address of PbcModule is the loaded address of pbc file
class PbcScript {
 public:
  friend PbcFunction;

  uint64_t GetHashValue() const { return header.pbc_hash_; }

  PbcFunction GetFunction(uint32_t index) const {
    P_CHECK(index < NumberOfFunctions());
    auto address = FunctionTableAddress() + index * sizeof(FunctionHeader);
    return PbcFunction(address);
  }

  PbcFunction GetTopLevelFunction() const {
    return GetFunction(header.top_level_func_id_);
  }
  uint32_t GetTopLevelFuncId() const { return header.top_level_func_id_; }
  uint32_t NumberOfFunctions() const { return header.function_count_; }
  uint32_t NumberOfScopes() const { return header.scope_count_; }
  uint32_t NumberOfTopDeclarations() const {
    return header.top_declarations_count_;
  }
  StringRef GetTopDeclaration(uint32_t index) const {
    P_CHECK(index < NumberOfTopDeclarations());
    return reinterpret_cast<StringRef*>(TopDeclarationsAddress())[index];
  }
  template <typename T>
  std::string_view GetString(T string_ref) const {
    if constexpr (std::is_same_v<T, PbcShortStringRef>) {
      P_CHECK(string_ref.Kind() == PbcShortValueKind::kShortU8String);
    } else {
      P_CHECK(string_ref.Kind() == PbcValueKind::kU8String);
    }
    uintptr_t strAddr = StringStorageAddress() + string_ref.GetOffset();
    return std::string_view(reinterpret_cast<const char*>(strAddr),
                            string_ref.GetLength());
  }
  template <typename T>
  std::u16string_view GetU16String(T string_ref) const {
    if constexpr (std::is_same_v<T, PbcShortStringRef>) {
      P_CHECK(string_ref.Kind() == PbcShortValueKind::kShortU16String);
    } else {
      P_CHECK(string_ref.Kind() == PbcValueKind::kU16String);
    }
    uintptr_t strAddr = U16StringStorageAddress() + string_ref.GetOffset();
    return std::u16string_view(reinterpret_cast<const char16_t*>(strAddr),
                               string_ref.GetLength());
  }
  double GetDouble(DoubleRef double_ref) const {
    uintptr_t double_addr =
        DoubleStorageAddress() + double_ref.GetId() * sizeof(double);
    return *reinterpret_cast<double*>(double_addr);
  }
  std::string_view GetBigInt(BigIntRef bigint_ref) const {
    P_CHECK(bigint_ref.Kind() == PbcValueKind::kBigInt);
    uintptr_t bigIntAddr = BigIntStorageAddress() + bigint_ref.GetOffset();
    return std::string_view(reinterpret_cast<const char*>(bigIntAddr),
                            bigint_ref.GetLength());
  }
  PbcArrayLiteral GetArrayLiteral(LiteralRef literal_ref) const {
    P_CHECK(literal_ref.Kind() == PbcValueKind::kArrayLiteral);
    uintptr_t literalAddr = LiteralStorageAddress() + literal_ref.GetOffset();
    return PbcArrayLiteral(literalAddr);
  }
  PbcArrayLiteral GetArrayLiteral(PbcShortLiteralRef literal_ref) const {
    P_CHECK(literal_ref.Kind() == PbcShortValueKind::kShortArrayLiteral);
    uintptr_t literalAddr = LiteralStorageAddress() + literal_ref.GetOffset();
    return PbcArrayLiteral(literalAddr);
  }
  PbcDefaultObjectLiteral GetDefaultObjectLiteral(
      LiteralRef literal_ref) const {
    P_CHECK(literal_ref.Kind() == PbcValueKind::kDefaultObjectLiteral);
    uintptr_t literalAddr = LiteralStorageAddress() + literal_ref.GetOffset();
    return PbcDefaultObjectLiteral(literalAddr);
  }
  PbcDefaultObjectLiteral GetDefaultObjectLiteral(
      PbcShortLiteralRef literal_ref) const {
    P_CHECK(literal_ref.Kind() == PbcShortValueKind::kShortObjectLiteral);
    uintptr_t literalAddr = LiteralStorageAddress() + literal_ref.GetOffset();
    return PbcDefaultObjectLiteral(literalAddr);
  }
  PbcObjectLiteral GetObjectLiteral(LiteralRef literal_ref) const {
    P_CHECK(literal_ref.Kind() == PbcValueKind::kObjectLiteral);
    uintptr_t literalAddr = LiteralStorageAddress() + literal_ref.GetOffset();
    return PbcObjectLiteral(literalAddr);
  }
  PbcClassLiteral GetClassLiteral(LiteralRef literal_ref) const {
    P_CHECK(literal_ref.Kind() == PbcValueKind::kClassLiteral);
    uintptr_t literalAddr = LiteralStorageAddress() + literal_ref.GetOffset();
    return PbcClassLiteral(literalAddr);
  }

  PbcTemplateLiteral GetTemplateLiteral(LiteralRef literal_ref) const {
    P_CHECK(literal_ref.Kind() == PbcValueKind::kTemplateLiteral);
    uintptr_t literalAddr = LiteralStorageAddress() + literal_ref.GetOffset();
    return PbcTemplateLiteral(literalAddr);
  }
  PbcScope GetFirstScope() const { return PbcScope(ScopeStorageAddress()); }
  PbcScope GetPbcScope(uint32_t scope_id) const {
    P_CHECK(scope_id < NumberOfScopes());
    return PbcScope(ScopeStorageAddress() + scope_id * sizeof(RawScope));
  }
  PbcLocalVar GetLocalVar(uint32_t index) const {
    uintptr_t LocalVarAddr =
        LocalVarStorageAddress() + index * sizeof(LocalVar);
    P_CHECK(LocalVarAddr + sizeof(LocalVar) <= FunctionTableAddress());
    return PbcLocalVar(LocalVarAddr);
  }
  const PbcHeader& GetHeader() const { return header; }
  void Dump() const;
  void DumpHeader() const;
  void DumpScopes() const;

 private:
  uintptr_t BaseAddress() const { return reinterpret_cast<uintptr_t>(this); }
  uintptr_t FunctionTableAddress() const {
    return BaseAddress() + header.function_table_offset_;
  }
  uintptr_t BytecodeStorageAddress() const {
    return BaseAddress() + header.bytecode_storage_offset_;
  }
  uintptr_t TopDeclarationsAddress() const {
    return BaseAddress() + header.top_declarations_offset_;
  }
  uintptr_t StringStorageAddress() const {
    return BaseAddress() + header.string_storage_offset_;
  }
  uintptr_t U16StringStorageAddress() const {
    return BaseAddress() + header.u16string_storage_offset_;
  }
  uintptr_t DoubleStorageAddress() const {
    return BaseAddress() + header.double_table_offset_;
  }
  uintptr_t BigIntStorageAddress() const {
    return BaseAddress() + header.bigint_storage_offset_;
  }
  uintptr_t LiteralStorageAddress() const {
    return BaseAddress() + header.literal_storage_offset_;
  }
  uintptr_t ScopeStorageAddress() const {
    return BaseAddress() + header.scope_storage_offset_;
  }
  uintptr_t LocalVarStorageAddress() const {
    return ScopeStorageAddress() + header.scope_count_ * sizeof(RawScope);
  }

  PbcHeader header;
};

}  // namespace portable

#endif  // PORTABLE_FORMAT_HELPER_H_